Skip to content

DO NOT MERGE: fix(engine-runner): send StopCode.Ok for engine-initiated stops#4861

Open
abcxff wants to merge 3 commits intomainfrom
abc-2.2.1-patch
Open

DO NOT MERGE: fix(engine-runner): send StopCode.Ok for engine-initiated stops#4861
abcxff wants to merge 3 commits intomainfrom
abc-2.2.1-patch

Conversation

@abcxff
Copy link
Copy Markdown
Contributor

@abcxff abcxff commented Apr 30, 2026

DO NOT MERGE

@railway-app
Copy link
Copy Markdown

railway-app Bot commented Apr 30, 2026

🚅 Deployed to the rivet-pr-4861 environment in rivet-frontend

Service Status Web Updated (UTC)
website 😴 Sleeping (View Logs) Web May 2, 2026 at 11:05 pm
frontend-cloud 😴 Sleeping (View Logs) Web May 2, 2026 at 10:50 pm
frontend-inspector 😴 Sleeping (View Logs) Web May 2, 2026 at 7:43 pm
kitchen-sink ❌ Build Failed (View Logs) Web May 1, 2026 at 7:28 pm
mcp-hub ✅ Success (View Logs) Web Apr 30, 2026 at 11:04 pm
ladle ❌ Build Failed (View Logs) Web Apr 30, 2026 at 11:03 pm

@abcxff abcxff changed the title fix(engine-runner): send StopCode.Ok for engine-initiated stops DO NOT MERGE: fix(engine-runner): send StopCode.Ok for engine-initiated stops Apr 30, 2026
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 30, 2026

Code Review

Overview

This PR fixes a bug in engine/sdks/typescript/runner where engine-initiated CommandStopActor commands were reporting StopCode.Error to pegboard instead of StopCode.Ok. The consequence is pegboard deciding to sleep (reschedule) the actor when it should have destroyed it. The fix sets stopIntentSent = true before forceStopActor for the engine-commanded stop path, while deliberately leaving the #handleLost path unchanged so unresponsive actors still surface as errors.


Correctness

The fix is logically sound. The #sendActorStateUpdate method at line 1297 already gates on actor.stopIntentSent || this.#draining to choose between StopCode.Ok and StopCode.Error. The only missing piece was that #handleCommandStopActor never set the flag, causing all engine-commanded stops to fall through to StopCode.Error.

The comment is well-written and explains the non-obvious invariant — specifically why #handleLost intentionally omits the mark.


Issues and Observations

1. Targets a deprecated path

Per CLAUDE.md, engine/sdks/typescript/runner is explicitly listed as deprecated:

engine/packages/pegboard-runner/, engine/sdks/typescript/runner, rivetkit-typescript/packages/engine-runner/, and associated runner workflows are deprecated. All new actor hosting work targets engine/packages/pegboard-envoy/ exclusively.

If active workloads still run on this runner, the fix is necessary. If not, merging it to a deprecated path has limited value and could mislead contributors.

2. Parity gap: rivetkit-typescript/packages/engine-runner has the same bug

rivetkit-typescript/packages/engine-runner/src/mod.ts line 1190 has the identical issue — #handleCommandStopActor calls forceStopActor without setting stopIntentSent:

async #handleCommandStopActor(commandWrapper: protocol.CommandWrapper) {
    const stopCommand = commandWrapper.inner.val as protocol.CommandStopActor;
    const actorId = commandWrapper.checkpoint.actorId;
    const generation = commandWrapper.checkpoint.generation;

    await this.forceStopActor(actorId, generation);  // stopIntentSent never set
}

CLAUDE.md requires both TypeScript runners to be kept at parity. If this fix is worth landing, it should also be applied to the rivetkit engine-runner in the same change.

3. Rust envoy-client is unaffected by design

The Rust path (engine/sdks/rust/envoy-client/src/actor.rs) uses a different mechanism — it derives stop_code from ctx.error.is_some() and defaults to StopCode::Ok for a clean ToActor::Stop. Engine-commanded stops with no error will naturally produce StopCode::Ok, so no parity action is needed there.

4. StopCode.Error for failed actor start

Line 1215 also calls forceStopActor without setting stopIntentSent on actor start failure. That path correctly produces StopCode.Error (the actor didn't start cleanly), so this is intentional, but worth confirming it's deliberate.


Minor Nit

The comment on line 1226–1229 is helpful, but the #handleLost reference uses a private method name (#handleLost) — acceptable since this is in the same class, but it could drift if the method is renamed.


Summary

The fix is correct and the code change is small and clean. The two blockers before merging are:

  1. Confirm whether this deprecated runner is still in use and whether the fix is actually needed.
  2. Apply the same fix to rivetkit-typescript/packages/engine-runner/src/mod.ts to maintain parity.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 30, 2026

More templates

@rivetkit/cloudflare-workers

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/cloudflare-workers@4861

@rivetkit/framework-base

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/framework-base@4861

@rivetkit/next-js

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/next-js@4861

@rivetkit/react

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/react@4861

rivetkit

pnpm add https://pkg.pr.new/rivet-dev/rivet/rivetkit@4861

@rivetkit/sql-loader

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/sql-loader@4861

@rivetkit/sqlite-vfs

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/sqlite-vfs@4861

@rivetkit/traces

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/traces@4861

@rivetkit/workflow-engine

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/workflow-engine@4861

@rivetkit/virtual-websocket

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/virtual-websocket@4861

@rivetkit/engine-runner

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/engine-runner@4861

@rivetkit/engine-runner-protocol

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/engine-runner-protocol@4861

commit: 4901152

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant